import { $set } from '@feng3d/serialization';
import { AnimationAction3D, AnimationClip3D, AnimationMixer3D, Clock, Color, Fog3D, MeshPhongMaterial3D, Node3D, PerspectiveCamera3D, PlaneGeometry3D, Scene3D, StatsComponent, WebGLRenderer3D } from 'feng3d-three';
import GUI from 'lil-gui';
import { GLTFLoader3D } from '../../extension/loaders/GLTFLoader3D';

let container: HTMLDivElement;
let stats: StatsComponent;
let clock: Clock;
let gui: GUI;
let mixer: AnimationMixer3D;
let actions: { [name: string]: AnimationAction3D };
let activeAction: AnimationAction3D;
let previousAction: AnimationAction3D;
let camera: PerspectiveCamera3D;
let scene: Scene3D;
let renderer: WebGLRenderer3D;
let model: Node3D;
let face;

const api = { state: 'Walking' };

init();
animate();

function init()
{
    container = document.createElement('div');
    document.body.appendChild(container);

    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 45, aspect: window.innerWidth / window.innerHeight, near: 0.25, far: 100 });
    camera.entity.position.set(-5, 3, 10);
    camera.entity.lookAt(0, 2, 0);

    scene = new Node3D().addComponent('Scene3D');
    scene.background = new Color(0xe0e0e0);
    scene.fog = new Fog3D(0xe0e0e0, 20, 100);

    clock = new Clock();

    // lights

    const hemiLight = new Node3D().addComponent('HemisphereLight3D', { color: { value: 0xffffff }, groundColor: { value: 0x444444 } });
    hemiLight.entity.position.set(0, 20, 0);
    scene.entity.addChild(hemiLight.entity);

    const dirLight = new Node3D().addComponent('DirectionalLight3D', { color: { value: 0xffffff } });
    dirLight.entity.position.set(0, 20, 10);
    scene.entity.addChild(dirLight.entity);

    // ground

    const mesh = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new PlaneGeometry3D(), { width: 2000, height: 2000 }),
        material: $set(new MeshPhongMaterial3D(), { color: { value: 0x999999 }, depthWrite: false })
    });
    mesh.entity.rotation.x = -Math.PI / 2;
    scene.entity.addChild(mesh.entity);

    const grid = new Node3D().addComponent('GridHelper3D', { size: 200, divisions: 40, color1: 0x000000, color2: 0x000000 });
    grid.material.opacity = 0.2;
    grid.material.transparent = true;
    scene.entity.addChild(grid.entity);

    // model

    const loader = new GLTFLoader3D();
    loader.load('../../models/gltf/RobotExpressive/RobotExpressive.glb', function (gltf)
    {
        model = gltf.scene;
        scene.entity.addChild(model);

        createGUI(model, gltf.animations);
    }, undefined, function (e)
    {
        console.error(e);
    });

    renderer = scene.addComponent('WebGLRenderer3D', { parameters: { antialias: true } });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.outputEncoding = 'sRGBEncoding';
    container.appendChild(renderer.domElement);

    window.addEventListener('resize', onWindowResize);

    // stats
    stats = scene.addComponent('StatsComponent');
    container.appendChild(stats.dom);
}

function createGUI(model: Node3D, animations: AnimationClip3D[])
{
    const states = ['Idle', 'Walking', 'Running', 'Dance', 'Death', 'Sitting', 'Standing'];
    const emotes = ['Jump', 'Yes', 'No', 'Wave', 'Punch', 'ThumbsUp'];

    gui = new GUI();

    mixer = new AnimationMixer3D(model);

    actions = {};

    for (let i = 0; i < animations.length; i++)
    {
        const clip = animations[i];
        const action = mixer.clipAction(clip);
        actions[clip.name] = action;

        if (emotes.indexOf(clip.name) >= 0 || states.indexOf(clip.name) >= 4)
        {
            action.clampWhenFinished = true;
            action.loop = 'LoopOnce';
        }
    }

    // states

    const statesFolder = gui.addFolder('States');

    const clipCtrl = statesFolder.add(api, 'state').options(states);

    clipCtrl.onChange(function ()
    {
        fadeToAction(api.state, 0.5);
    });

    statesFolder.open();

    // emotes

    const emoteFolder = gui.addFolder('Emotes');

    function createEmoteCallback(name: string)
    {
        api[name] = function ()
        {
            fadeToAction(name, 0.2);

            mixer.on('finished', restoreState);
        };

        emoteFolder.add(api, name);
    }

    function restoreState()
    {
        mixer.off('finished', restoreState);

        fadeToAction(api.state, 0.2);
    }

    for (let i = 0; i < emotes.length; i++)
    {
        createEmoteCallback(emotes[i]);
    }

    emoteFolder.open();

    // expressions

    face = model.getObjectByName('Head_4');

    const expressions = Object.keys(face.morphTargetDictionary);
    const expressionFolder = gui.addFolder('Expressions');

    for (let i = 0; i < expressions.length; i++)
    {
        expressionFolder.add(face.morphTargetInfluences, `${i}`, 0, 1, 0.01).name(expressions[i]);
    }

    activeAction = actions['Walking'];
    activeAction.play();

    expressionFolder.open();
}

function fadeToAction(name: string, duration: number)
{
    previousAction = activeAction;
    activeAction = actions[name];

    if (previousAction !== activeAction)
    {
        previousAction.fadeOut(duration);
    }

    activeAction
        .reset()
        .setEffectiveTimeScale(1)
        .setEffectiveWeight(1)
        .fadeIn(duration)
        .play();
}

function onWindowResize()
{
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

//

function animate()
{
    const dt = clock.getDelta();

    if (mixer) mixer.update(dt);

    requestAnimationFrame(animate);

    renderer.render(scene, camera);

    stats.update();
}
